package it.uniba.di.mas.jpnde.behaviour;

import java.io.IOException;

import it.uniba.di.mas.jpnde.core.ITransitionTask;
import it.uniba.di.mas.jpnde.core.PNContext;
import it.uniba.di.mas.jpnde.core.ScriptFunction;
import it.uniba.di.mas.jpnde.core.TaggedValue;
import it.uniba.di.mas.jpnde.core.Token;
import it.uniba.di.mas.jpnde.core.Transition;
import it.uniba.di.mas.jpnde.impl.AbstractTransitionTask;
import jade.core.AID;
import jade.core.behaviours.Behaviour;
import jade.lang.acl.ACLMessage;
import jade.util.leap.ArrayList;

public class PNTransitionBehaviour extends Behaviour{
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;
	
	private final static short AWAIT_TRANS=0;
	private final static short INIT_TRANS=1;
	private final static short AWAIT_EXECUTE_TRANS=2;
	private final static short END=3;
	private final static short ERROR=4;
	
	private int state=AWAIT_TRANS;
	
	private Transition trans=null;
	private ITransitionTask task=null;
	private String errorMessage="";
	private ArrayList ctxQueue=new ArrayList();
	private Thread taskThread=null;
	private TransitionRun transRun=null;
	
	@Override
	public void action() {
		// TODO Auto-generated method stub
		switch(state){
		case AWAIT_TRANS:
			receiveTrans();
			break;
		case INIT_TRANS:
			initTrans();
			break;
		case AWAIT_EXECUTE_TRANS:
			awaitExecute();
			break;
		}	
	}

	@Override
	public boolean done() {
		// TODO Auto-generated method stub
		return state==ERROR||state==END;
	}

	private void receiveTrans(){
		System.out.println("PNTransitionBehaviour.receiveTrans ");
		ACLMessage message=myAgent.receive();
		try
			{
			if (message!=null)
				{
				ACLMessage reply=message.createReply();
				if ((message.getPerformative()!=ACLMessage.REQUEST)||
				    !(message.getContentObject() instanceof Transition))
					{
						state=ERROR;
						reply.setPerformative(ACLMessage.FAILURE);
					}	
				else
					{
						trans=(Transition)message.getContentObject();
						state=INIT_TRANS;
						reply.setPerformative(ACLMessage.INFORM);
					}
				myAgent.send(reply);
				}
			else
				block();
			}
		catch(Exception e)
			{e.printStackTrace();}
	
	}
	
	private void initTrans(){
		System.out.println("PNTransitionBehaviour.initTrans :"+trans.getId());
		try
		{
		if (trans.getImplClass().equals(""))
			{
			task=new AbstractTransitionTask()
				{
					@Override
					public Token exec(PNContext context) 
						throws Exception{
						// TODO Auto-generated method stub
						System.out.println("TransitionTask.exec :"+transition.getId());
						System.out.println("TransitionTask.exec :tokens [In] > "+context.getTokens());
						Token token=new Token(transition.getId());
						if (transition.getScript()!=null){
							// eseguo lo script
							ArrayList vars=new ArrayList();
							vars.add(new TaggedValue("tokenOut",token));
							vars.add(new TaggedValue("context",context));
							transition.getScript().execute(ScriptFunction.ON_ACTIVATION, vars);
							token=(Token)transition.getScript().getReaturnValues().get("tokenOut");
							}
						return token;
					}
				};
			}	
		else
			{
			Class cl=Class.forName(trans.getImplClass());
			task=(ITransitionTask)cl.newInstance();
			}
		
		task.init(this, trans);	
		}
		catch (Throwable t)
			{
			t.printStackTrace();
			// Invio messaggio di errore al manager
			errorMessage="PNTransitionBehaviour.initTrans "+trans.getId()+":"+t.getMessage();
			state=ERROR;
			return;
			}
		
		state=AWAIT_EXECUTE_TRANS;
	}
	
	private void awaitExecute(){
		//System.out.println("PNTransitionBehaviour.awaitExecute :"+trans.getId());
		ACLMessage message=myAgent.receive();
		if (message!=null)
			{
			  // Elaboro il messaggio
			  if (message.getSender().getName().equals(trans.getManagerId()))
			  	{
				  switch(message.getPerformative())
				  {
				  case ACLMessage.REQUEST:
				    try {
						ctxQueue.add((PNContext)message.getContentObject());  
					  	}
					  	catch(Throwable t){
					  		t.printStackTrace();
							// Invio messaggio di errore al manager
							
							errorMessage="PNTransitionBehaviour.awaitExecute "+
									trans.getId()+":"+t.getMessage();
							state=ERROR;
							return;
					  }
					  break;
				  case ACLMessage.CANCEL:
					  state=END;
					  break;
				  default: 	  
				       System.out.println("PNTransitionBehaviour.awaitExecute :"+trans.getId()+
					      " performativa <"+message.getPerformative()+"> not recognized !");
				  	}
			  	}
			  else
				  System.out.println("PNTransitionBehaviour.initTrans :"+trans.getId()+
						  " sender <"+message.getSender().getName()+"> not recognized !");
			}
		else
			if (ctxQueue.size()==0&&taskThread==null)
				block();
			
		if (taskThread==null)
			{
				if (ctxQueue.size()>0){					
					transRun=new TransitionRun(task,(PNContext)ctxQueue.get(0));
					taskThread=new Thread(transRun);
					ctxQueue.remove(0);
					taskThread.start();
				}
			}
		else if 
			(!taskThread.isAlive()){
				if (!transRun.isInError())
					{
					ACLMessage retMsg=new ACLMessage(ACLMessage.INFORM);
					retMsg.setPerformative(ACLMessage.INFORM);
					retMsg.addReceiver(new AID(trans.getManagerId(),AID.ISGUID));
					try {
						trans.setReturnToken(transRun.getToken());
						retMsg.setContentObject(trans);
						} catch (IOException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
						errorMessage=e.getMessage();
						state=ERROR;
						return;
					}
					myAgent.send(retMsg);
					}
				else
					{
					errorMessage=transRun.getErrorMessage();
					state=ERROR;
					return;
					}
				taskThread=null;
			}
	}
	
	public int onEnd() {
		System.out.println("PNTransitionBehaviour.onEnd :"+trans.getId());
		int ret=super.onEnd();
		if (state!=ERROR)
			task.end();
		else
			{
			if (!errorMessage.equals(""))
				{
				ACLMessage message=new ACLMessage(ACLMessage.FAILURE);
				message.addReceiver(new AID(trans.getManagerId(),AID.ISGUID));
				message.setContent(errorMessage);
				myAgent.send(message);
				}
			}
		myAgent.doDelete();
		return ret;
	}
	
	public class TransitionRun implements Runnable
	{
		private ITransitionTask task=null;
		private PNContext context=null;
		private Token token=null;
		
		private boolean error=false;
		private String errorMessage="";
		
		public TransitionRun(ITransitionTask task, PNContext context)
		{
			this.task=task;
			this.context=context;
		}
		
		@Override
		public void run() {
			// TODO Auto-generated method stub
			try {
				token=task.exec(context);
			} catch (Throwable e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
				error=true;
				errorMessage="TransitionRun.run():"+e.getMessage();
			}
		}
		
		public boolean isInError(){
			return error;
		}
		public String getErrorMessage(){
			return errorMessage;
		}
		public Token getToken(){
			return token;
		}
	}
}
